package com.java.collection.set;

import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

import org.junit.Test;

import com.collection.model.Student;

/**
 * Collection集合
 * 		|--List
 * 			有序（插入和取出顺序一致），元素可重复
 * 		|--Set
 * 			无序（插入和取出顺序不一致），元素唯一
 * 			|--HashSet
 * 				元素无序（插入和取出顺序不一致），唯一
 * 				此类实现 Set接口，由哈希表（实际上是一个 HashMap实例）支持。它不保证 set的迭代顺序；特别是它不保证该顺序恒久不变。此类允许使用 null元素。 
 * 				哈希表：
 * 					是一个元素为链表的数组，综合了数组和链表的好处；
 * 					哈希表依赖于哈希值进行存储；
 * 					添加功能底层依赖两个方法：
 * 						int hashCode();
 * 						boolean equals(Object obj) ;
 * 				即底层是哈希表结构。
 * 			|--LinkedHashSet
 * 				元素有序（插入和取出顺序一致），唯一。
 * 					由链表保证元素的有序（插入和取出的顺序是一致的）；
 * 					由哈希表保证元素的唯一。
 * 				即底层是哈希表+链表结构。
 * 			|--TreeSet
 * 				元素可排序，唯一。
 * 				使用元素的自然顺序对元素进行排序，或者根据创建 set时提供的 Comparator进行排序，具体取决于使用的构造方法。 
 * 				即能够对元素按照某种规则进行排序：
 * 				排序有两种：
 * 					1、自然排序(元素具备比较器)
 * 							让元素所属的类实现自然排序接口Comparable
 * 					2、比较器排序(集合具备比较器)
 * 							让集合的构造方法接收一个比较器接口的子类对象Comparator
 * 				自然排序与比较器排序的区别——以自定义对象Student为例
 * 					1、自然排序
 * 						1、Student对象需实现Comparable接口，这个接口表示的就是自然排序。
 * 						2、创建TreeSet<Student>集合对象，默认使用TreeSet<Student>类的无参构造器
 * 							TreeSet<Student> set = new TreeSet<Student>() ;
 * 					2、比较器排序
 * 						1、创建Comparator<Student>匿名内部类
 * 						2、调用TreeSet的有参构造方法，穿入Comparator<Student>匿名内部类作为其参数
 * 						TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {重写compare()方法——实现自定义比较逻辑}
 * 				排序原理分析：
 * 					TreeSet：底层是红黑二叉树结构(红黑树是一种自平衡的二叉树)。
 * 						1、元素的存储：
 * 							第一个元素作为根节点存储；
 * 							从第二个元素开始，每个元素从根节点开始比较
 * 								>根节点：作为右孩子；
 * 								<根节点：作为左孩子；
 * 								=根节点：不做处理。
 * 						2、元素的读取（前序遍历、中序遍历、后序遍历）：
 * 							中序遍历读取元素：
 * 								从根节点开始，按照左、中、右的原则依次取出元素即可。
 * @author LC
 */
public class SetHandle {
	
	/**
	 * 测试01
	 * 探究Set保证元素唯一的原理
	 * 		通过查看add方法的源码，发现这个方法底层依赖于两个方法：hashCode()和equals()
	 * 		添加元素步骤：
	 * 			先看hashCode()的值是否相同
	 * 				相同：调用equals()方法
	 * 					返回true:说明元素重复，不添加
	 * 					返回false:说明元素不重复，添加到集合
	 * 				不同：直接将元素添加到集合。
	 * 		如果类没有重写这两个方法，默认使用Object的hashCode()和equals()方法，一般不相同。
	 * 		String类重写了hashCode()和equals()方法，因此，可以保证集合中的元素唯一
	 */
	@Test
	public void testHashSet_01() {
		
		Set<String> set = new HashSet<>() ;
		
		set.add("001") ;
		set.add("002") ;
		set.add("003") ;
		set.add("001") ;
		set.add("002") ;
		
		for (String element : set) {
			System.out.println(element);
		}
	}
	
	/**
	 * 测试02：存储自定义对象，并保证元素的唯一性
	 * 		要求：如果两个对象的成员变量都相同，则视为同一个元素。
	 * 		说明：自定义对象类需实现hashCode()和equals()方法。
	 */
	@Test
	public void testHashSet_02() {
		
		Set<Student> set = new HashSet<>() ;
		
		Student student01 = new Student("张三01",21) ;
		Student student02 = new Student("张三02",21) ;
		Student student03 = new Student("张三01",21) ;
		Student student04 = new Student("张三02",21) ;
		
		set.add(student01) ;
		set.add(student02) ;
		set.add(student03) ;
		set.add(student04) ;
		
		for (Student student : set) {
			System.out.println(student);
		}
	}
	
	/**
	 * 测试03：
	 * 		测试LinkedHashSet集合元素的有序，唯一性
	 */
	@Test
	public void testLinkedHashSet() {
		
		Set<String> set = new LinkedHashSet<>() ;
		
		set.add("002") ;
		set.add("001") ;
		set.add("003") ;
		set.add("004") ;
		
		for (String element : set) {
			System.out.println(element);
		}
	}
	
	/**
	 * TreeSet测试01——自然排序
	 * 		排序原理分析：
	 * 			真正的比较是依赖于元素的compareTo()方法，而这个方法被定义在Comparable里边。
	 * 			所以，如果想重写该方法，就必须先实现Comparable接口，这个接口表示的就是自然排序。
	 */
	@Test
	public void testTreeSet_01() {
		
		Set<String> set = new TreeSet<>() ;
		
		set.add("001") ;
		set.add("003") ;
		set.add("002") ;
		set.add("005") ;
		set.add("004") ;
		
		for (String element : set) {
			System.out.println(element);
		}
	}
	
	/**
	 * TreeSet测试02——自定义对象自然排序
	 * 		1、Student对象需实现Comparable接口，且重写compareTo方法：实现按学生的姓名的长度进行排序
	 * 		2、TreeSet<Student> set = new TreeSet<Student>() ;
	 */
	@Test
	public void testTreeSet_02() {
		
		TreeSet<Student> set = new TreeSet<Student>() ;
		
		Student student01 = new Student("张三01",21) ;
		Student student02 = new Student("张三001",22) ;
		Student student03 = new Student("张三0001",23) ;
		Student student04 = new Student("张三00001",24) ;
		
		set.add(student03) ;
		set.add(student02) ;
		set.add(student01) ;
		set.add(student04) ;
		
		for (Student student : set) {
			System.out.println(student);
		}
	}
	
	/**
	 * TreeSet测试02——Student对象按照自定义比较器Comparator进行排序
	 * 		1、创建Comparator<Student>匿名内部类
	 * 		2、调用TreeSet的有参构造方法，穿入Comparator<Student>匿名内部类作为其参数
	 * 			TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {重写compare()方法——实现自定义比较逻辑}
	 * 	
	 */
	@Test
	public void testTreeSet_03() {
		
		TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
			//创建Comparator比较器匿名内部类对象，重写器compare()方法——按Student对象的名称长度进行排序
			@Override
			public int compare(Student s1, Student s2) {
				//比较Studnet对象的名称长度
				int num = s1.getName().length() - s2.getName().length();
				//比较Student对象的名称内容
				int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num ;
				//比较Student对象的年龄
				int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2 ;
				return num3;
			}
		}) ;
		
		Student student01 = new Student("张三01",21) ;
		Student student02 = new Student("张三001",22) ;
		Student student03 = new Student("张三0001",23) ;
		Student student04 = new Student("张三00001",24) ;
		
		set.add(student03) ;
		set.add(student02) ;
		set.add(student01) ;
		set.add(student04) ;
		
		for (Student student : set) {
			System.out.println(student);
		}
		
	}

}
